/*
 * Copyright 1999,2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.compiler;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.util.StringTokenizer;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

import org.apache.jasper.JasperException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

/**
 * Main JSP compiler class. This class uses 7Bee for compiling.
 * 
 * @author Dmitriy Rogatkin
 */
public class BeeCompiler extends Compiler {
	 private final Log log = LogFactory.getLog(BeeCompiler.class); // must not be static

	protected static Object javacLock = new Object();

	private Class javaCompiler;

	static {
		System.setErr(new SystemLogHandler(System.err));
	}

	/**
	 * Compile the servlet from .java file to .class file
	 */
	protected void generateClass(String[] smap) throws FileNotFoundException, JasperException, Exception {

		long t1 = 0;
		if (log.isDebugEnabled())
			t1 = System.currentTimeMillis();

		List<String> parameters = new ArrayList<String>(20);
		parameters.add("-encoding");
		parameters.add(ctxt.getOptions().getJavaEncoding());

		String javaFileName = new File(ctxt.getServletJavaFileName()).getPath();
		String classpath = ctxt.getClassPath();

		String sep = File.pathSeparator; // System.getProperty("path.separator");

		StringBuffer errorReport = new StringBuffer();

		StringBuffer info = new StringBuffer();
		info.append("Compile: javaFileName=" + javaFileName + "\n");

		// Start capturing the System.err output for this thread
		SystemLogHandler.setThread();

		// Initializing classpath
		String path = System.getProperty("java.class.path");
		info.append("    cmd cp=" + path + "\n");
		info.append("    ctx cp=" + classpath + "\n");
		path += sep;
		path += classpath;

		if (log.isDebugEnabled())
			log.debug("Using classpath: " + path);

		parameters.add("-classpath");
		parameters.add(path);

		// Initializing sourcepath
		parameters.add("-sourcepath");
		parameters.add(options.getScratchDir().getPath());

		info.append("    work dir=" + options.getScratchDir() + "\n");

		// Initialize and set java extensions
		String extdirs = System.getProperty("java.ext.dirs");
		if (extdirs != null) {
			parameters.add("-extdirs");
			parameters.add(extdirs);
			info.append("    extension dir=" + extdirs + "\n");
		}

		if (ctxt.getOptions().getFork()) {
			String endorsed = System.getProperty("java.endorsed.dirs");
			if (endorsed != null) {
				parameters.add("-endorseddirs"); // "-J-Djava.endorsed.dirs="+endorsed
				parameters.add(endorsed);
				info.append("    endorsed dir=" + endorsed + "\n");
			} else {
				info.append("    no endorsed dirs specified\n");
			}
		}

		if (ctxt.getOptions().getClassDebugInfo())
			parameters.add("-g");

		Exception ie = null;

		// Set the Java compiler to use
		if (javaCompiler == null) {
			// assumption, there is no dynamic changing Java compiler 
			String compiler = options.getCompiler();
			if (compiler == null)
				compiler = "com.sun.tools.javac.Main";
			// verify compiler
			try {
				javaCompiler = Class.forName(compiler);
			} catch (ClassNotFoundException cnfe) {
				// try to figure out class path to compiler
				String compileClassPath = System.getProperty("java.home");
				if (compileClassPath == null)
					try {
						compileClassPath = System.getenv("JAVA_HOME");
						if (compileClassPath == null)
							compileClassPath = System.getenv("java_home");
					} catch (SecurityException se) {

					}

				if (compileClassPath != null) {
					// HACK for now
					compileClassPath = compileClassPath.replace("jre", "jdk");
					compileClassPath += "/lib/tools.jar";
					info.append("    checking default compiler in " + compileClassPath + "\n");
					try {
						javaCompiler = Class.forName(compiler, true, new URLClassLoader(new URL[] { new URL("file",
								"localhost", compileClassPath) }));
					} catch (Error er) {
						log.error("Setting up Java compiler error ", er);
					} catch (Exception ex) {
						log.error("Setting up Java compiler exception ", ex);
					}
				} else
					info.append("    no Java home path specified\n");
			}
			info.append("    compiler=" + compiler + "\n");
		}

		if (options.getCompilerTargetVM() != null) {
			parameters.add("-target");
			parameters.add(options.getCompilerTargetVM());
			info.append("   compilerTargetVM=" + options.getCompilerTargetVM() + "\n");
		}

		if (options.getCompilerSourceVM() != null) {
			parameters.add("-source");
			parameters.add(options.getCompilerSourceVM());
			info.append("   compilerSourceVM=" + options.getCompilerSourceVM() + "\n");
		}

		info.append("   JavaPath=" + ctxt.getJavaPath() + "\n");

		parameters.add(javaFileName);

		boolean compilationErrors = false;
		String errorCapture = null;
		if (javaCompiler != null || (newerJava()))
			try {
				Integer success;
				if (newerJava()) {
					JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
					//log.error("comp "+compiler);
					//for(String ps:parameters)
					  // log.error(ps);
              			         success = compiler.run(System.in, System.out, System.err, parameters
							.toArray(new String[parameters.size()]));
					//log.error("Compiled "+parameters+" with "+success);
				} else {
				Method cm = javaCompiler.getMethod("compile", new Class[] { String[].class });
				if (ctxt.getOptions().getFork()) {
					success = (Integer) cm.invoke(null, new Object[] { parameters
							.toArray(new String[parameters.size()]) });
				} else {
					synchronized (javacLock) {
						success = (Integer) cm.invoke(null, new Object[] { parameters.toArray(new String[parameters
								.size()]) });
					}
				}
}
				if (success.intValue() != 0)
					compilationErrors = true;
			} catch (Throwable t) {
				if (t instanceof ThreadDeath)
					throw (ThreadDeath) t;
				if (t instanceof InvocationTargetException)
					t = t.getCause();
				if (t instanceof Exception)
					ie = (Exception) t;
				else
					ie = new Exception(t);
				log.error("Javac exception ", t);
				log.error("Env: " + info.toString());
			} finally {
				// Stop capturing the System.err output for this thread
				errorCapture = SystemLogHandler.unsetThread();
			}
		if (compilationErrors && errorCapture != null) {
			errorReport.append(System.getProperty("line.separator"));
			errorReport.append(errorCapture);
		}

		if (!ctxt.keepGenerated()) {
			if (new File(javaFileName).delete() == false)
				log.error("Couldn't delete source: " + javaFileName);
		}

		if (compilationErrors || ie != null) {
			String errorReportString = errorReport.toString();
			log.error("Error compiling file: " + javaFileName + " " + errorReportString);
			JavacErrorDetail[] javacErrors = ErrorDispatcher.parseJavacErrors(errorReportString, javaFileName,
					pageNodes);
			if (javacErrors != null) {
				errDispatcher.javacError(javacErrors);
			} else {
				errDispatcher.javacError(errorReportString, ie);
			}
		}

		if (log.isDebugEnabled()) {
			long t2 = System.currentTimeMillis();
			log.debug("Compiled " + ctxt.getServletJavaFileName() + " " + (t2 - t1) + "ms");
		}

		if (ctxt.isPrototypeMode()) {
			return;
		}

		// JSR45 Support
		if (!options.isSmapSuppressed()) {
			log.debug("Install Smap " + (smap == null ? "null" : Arrays.toString(smap)));
			SmapUtil.installSmap(smap);
		}
	}
	
	final boolean newerJava() {
	     try {
			return Integer.parseInt(System.getProperty("java.specification.version")) > 10;
		} catch(Exception e) {
			return false;
		}
	}

    protected static class SystemLogHandler extends PrintStream {


        // ----------------------------------------------------------- Constructors


        /**
         * Construct the handler to capture the output of the given steam.
         */
        public SystemLogHandler(PrintStream wrapped) {
            super(wrapped);
            this.wrapped = wrapped;
        }


        // ----------------------------------------------------- Instance Variables


        /**
         * Wrapped PrintStream.
         */
        protected PrintStream wrapped = null;


        /**
         * Thread <-> PrintStream associations.
         */
        protected static ThreadLocal streams = new ThreadLocal();


        /**
         * Thread <-> ByteArrayOutputStream associations.
         */
        protected static ThreadLocal data = new ThreadLocal();


        // --------------------------------------------------------- Public Methods


        public PrintStream getWrapped() {
          return wrapped;
        }

        /**
         * Start capturing thread's output.
         */
        public static void setThread() {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            data.set(baos);
            streams.set(new PrintStream(baos));
        }


        /**
         * Stop capturing thread's output and return captured data as a String.
         */
        public static String unsetThread() {
            ByteArrayOutputStream baos = 
                (ByteArrayOutputStream) data.get();
            if (baos == null) {
                return null;
            }
            streams.set(null);
            data.set(null);
            return baos.toString();
        }


        // ------------------------------------------------------ Protected Methods


        /**
         * Find PrintStream to which the output must be written to.
         */
        protected PrintStream findStream() {
            PrintStream ps = (PrintStream) streams.get();
            if (ps == null) {
                ps = wrapped;
            }
            return ps;
        }


        // ---------------------------------------------------- PrintStream Methods


        public void flush() {
            findStream().flush();
        }

        public void close() {
            findStream().close();
        }

        public boolean checkError() {
            return findStream().checkError();
        }

        protected void setError() {
            //findStream().setError();
        }

        public void write(int b) {
            findStream().write(b);
        }

        public void write(byte[] b)
            throws IOException {
            findStream().write(b);
        }

        public void write(byte[] buf, int off, int len) {
            findStream().write(buf, off, len);
        }

        public void print(boolean b) {
            findStream().print(b);
        }

        public void print(char c) {
            findStream().print(c);
        }

        public void print(int i) {
            findStream().print(i);
        }

        public void print(long l) {
            findStream().print(l);
        }

        public void print(float f) {
            findStream().print(f);
        }

        public void print(double d) {
            findStream().print(d);
        }

        public void print(char[] s) {
            findStream().print(s);
        }

        public void print(String s) {
            findStream().print(s);
        }

        public void print(Object obj) {
            findStream().print(obj);
        }

        public void println() {
            findStream().println();
        }

        public void println(boolean x) {
            findStream().println(x);
        }

        public void println(char x) {
            findStream().println(x);
        }

        public void println(int x) {
            findStream().println(x);
        }

        public void println(long x) {
            findStream().println(x);
        }

        public void println(float x) {
            findStream().println(x);
        }

        public void println(double x) {
            findStream().println(x);
        }

        public void println(char[] x) {
            findStream().println(x);
        }

        public void println(String x) {
            findStream().println(x);
        }

        public void println(Object x) {
            findStream().println(x);
        }

    }

}
